package com.aboverock.core.shiro.authz.rest;

import static org.apache.shiro.util.StringUtils.split;

import com.aboverock.common.domain.Result;
import com.aboverock.common.utils.JsonMapper;
import com.aboverock.core.shiro.util.ShiroRestUrlUtil;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The Class CustomPermissionsAuthorizationFilter.
 *
 * @author Rock Wang
 */
public class CustomPermissionsAuthorizationFilter extends AuthorizationFilter {
    private static final Logger log = LoggerFactory
            .getLogger(CustomPermissionsAuthorizationFilter.class);

    protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();

    @Override
    public Filter processPathConfig(String path, String config) {
        String[] values = null;
        if (config != null) {
            values = split(config);
        }

        this.appliedPaths.put(path, values);
        return this;
    }

    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
            if (log.isTraceEnabled()) {
                log.trace(
                        "appliedPaths property is null or empty.  This Filter will passthrough immediately.");
            }
            return true;
        }

        String bestMatchPath = null;
        for (String path : this.appliedPaths.keySet()) {
            // If the path does match, then pass on to the subclass implementation for specific
            // checks
            // (first match 'wins'):
            if (pathsMatch(path, request)) {
                if (null == bestMatchPath || pathMatch(bestMatchPath, path)) {
                    bestMatchPath = path;
                }
            }
        }

        if (bestMatchPath != null) {
            log.trace(
                    "Current requestURI matches pattern '{}'.  Determining filter chain execution...",
                    bestMatchPath);
            Object config = this.appliedPaths.get(bestMatchPath);
            return isFilterChainContinued(request, response, bestMatchPath, config);
        }

        // no path matched, allow the request to go through:
        return true;
    }

    private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
            String path, Object pathConfig) throws Exception {

        if (isEnabled(request, response, path, pathConfig)) { // isEnabled check added in 1.2
            if (log.isTraceEnabled()) {
                log.trace(
                        "Filter '{}' is enabled for the current request under path '{}' with config [{}].  "
                                + "Delegating to subclass implementation for 'onPreHandle' check.",
                        new Object[] { getName(), path, pathConfig });
            }
            // The filter is enabled for this specific request, so delegate to subclass
            // implementations
            // so they can decide if the request should continue through the chain or not:
            return onPreHandle(request, response, pathConfig);
        }

        if (log.isTraceEnabled()) {
            log.trace(
                    "Filter '{}' is disabled for the current request under path '{}' with config [{}].  "
                            + "The next element in the FilterChain will be called immediately.",
                    new Object[] { getName(), path, pathConfig });
        }
        // This filter is disabled for this specific request,
        // return 'true' immediately to indicate that the filter will not process the request
        // and let the request/response to continue through the filter chain:
        return true;
    }

    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response,
            Object mappedValue) throws IOException {

        Subject subject = getSubject(request, response);
        String[] perms = (String[]) mappedValue;

        boolean isPermitted = true;
        if (perms != null && perms.length > 0) {
            if (perms.length == 1) {
                if (!subject.isPermitted(perms[0])) {
                    isPermitted = false;
                }
            } else {
                isPermitted = false;
                for (String perm : perms) {
                    if (subject.isPermitted(perm)) {
                        isPermitted = true;
                        break;
                    }
                }
            }
        }

        return isPermitted;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
            throws IOException {
        if (log.isDebugEnabled()) {
            Subject subject = getSubject(request, response);
            log.debug("Data-Smart: {}无权访问{}", subject.getPrincipal(),
                    getPathWithinApplication(request, true));
        }

        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        JsonMapper jsonMapper = JsonMapper.getInstance();
        try (PrintWriter out = response.getWriter()) {
            Result result = Result.fail("ResultEnum.AUTHORIZATION_FAIL");
            out.append(jsonMapper.toJson(result));
        }

        return false;
    }

    @Override
    protected boolean pathsMatch(String path, ServletRequest request) {
        String requestURI = ShiroRestUrlUtil.isRestUrl(path)
                ? getPathWithinApplication(request, true)
                : getPathWithinApplication(request, false);
        log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path,
                requestURI);
        return pathMatcher.matches(path, requestURI);
    }

    private String getPathWithinApplication(ServletRequest request, boolean getMethod) {
        if (getMethod) {
            return ShiroRestUrlUtil.buildRestUrl(WebUtils.toHttp(request).getMethod(),
                    WebUtils.getPathWithinApplication(WebUtils.toHttp(request)));
        }
        return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
    }

    private boolean pathMatch(String pattern, String path) {
        if (ShiroRestUrlUtil.isRestUrl(pattern)) {
            return pathMatcher.matches(pattern, path);
        }
        return pathMatcher.matches(pattern, ShiroRestUrlUtil.getRawUrl(path));
    }

}
